#include "lexer.h"
#include "Diagnosis.h"
#include <cstring>
#include <cassert>
#include <iostream>

void Lexer::GetNextToken() {
    // 1 跳过空格
    SkipWhiteSpace();

    SourceCodeLocation location;
    location.Line = Line;
    location.Col = NextCharIdx - LineHead - 1;

    TokenKind kind;
    

    int value = 0, startPos = NextCharIdx - 1;
    if (CurrentChar == '\0') {
        kind = TokenKind::Eof;
        GetNextChar();
    } 
    else if (CurrentChar == '+') {
        kind = TokenKind::Plus;
        GetNextChar();
    }
    else if (CurrentChar == '-') {
        kind = TokenKind::Minus;
        GetNextChar();
    }
    else if (CurrentChar == '*') {
        kind = TokenKind::Star;
        GetNextChar();
    }
    else if (CurrentChar == '/') {
        kind = TokenKind::Slash;
        GetNextChar();
    } 
    else if (CurrentChar == '&') {
        kind = TokenKind::Amp;
        GetNextChar();
    }
    else if (CurrentChar == '(') {
        kind = TokenKind::LParent;
        GetNextChar();
    }
    else if (CurrentChar == ')') {
        kind = TokenKind::RParent;
        GetNextChar();   
    }
    else if (CurrentChar == '[') {
        kind = TokenKind::LBacket;
        GetNextChar();
    }
    else if (CurrentChar == ']') {
        kind = TokenKind::RBacket;
        GetNextChar();   
    }
    else if (CurrentChar == '{') {
        kind = TokenKind::LBrace;
        GetNextChar();
    }
    else if (CurrentChar == '}') {
        kind = TokenKind::RBrace;
        GetNextChar();
    }
    else if (CurrentChar == ',') {
        kind = TokenKind::Comma;
        GetNextChar();
    }
    else if (CurrentChar == ';') {
        kind = TokenKind::Semicolon;
        GetNextChar();
    }
    else if (CurrentChar == '=') {
        if (PeekChar(1) == '=') {
            GetNextChar();
            kind = TokenKind::Equal;
        } else{
            kind = TokenKind::Assign;
        }
        GetNextChar();
    }
    else if (CurrentChar == '!') {
        if (PeekChar(1) == '=') {
            GetNextChar();
            kind = TokenKind::PipeEqual;
        } 
        else {
            DiagErr(SourceCode, location.Col, 
                "'!' is not supported. did you mean '!='?(line: %d)", location.Line);
        }
        GetNextChar();
    }
    else if (CurrentChar == '>') {
        if (PeekChar(1) == '=') {
            GetNextChar();
            kind = TokenKind::GreaterEqual;
        } else {
            kind = TokenKind::Greater;
        }
        GetNextChar();
    }
    else if (CurrentChar == '<') {
        if (PeekChar(1) == '=') {
            GetNextChar();
            kind = TokenKind::LesserEqual;
        } else {
            kind = TokenKind::Lesser;
        }
        GetNextChar();
    }
    else if (isdigit(CurrentChar)) {
        kind = TokenKind::Num;
        do {
            value = value * 10 + CurrentChar - '0';
            GetNextChar();
        } while(isdigit(CurrentChar));

    }
    else {
        if (IsLetter()) {
            while (IsLetterOrDigit()) {
                GetNextChar();
            }
            kind = TokenKind::Identifier;
            std::string_view content = SourceCode.substr(startPos, NextCharIdx - startPos - 1);
            if (content == "if") {
                kind = TokenKind::If;
            } else if (content == "else") {
                kind = TokenKind::Else;
            } else if (content == "while") {
                kind = TokenKind::While;
            } else if (content == "for") {
                kind = TokenKind::For;
            } else if (content == "return") {
                kind = TokenKind::Return;
            } else if (content == "char") {
                kind = TokenKind::Char;
            } else if (content == "short") {
                kind = TokenKind::Short;
            } else if (content == "int") {
                kind = TokenKind::Int;
            } else if (content == "long") {
                kind = TokenKind::Long;
            } else if (content == "sizeof") {
                kind = TokenKind::Sizeof;
            } 
        }
        else {
            DiagErr(SourceCode, location.Col, 
                "Current char '%c' is illegal!(line: %d)\n", 
                SourceCode[NextCharIdx - 1], location.Line); 
        } 
    }

    CurrentToken = std::make_shared<Token>();
    CurrentToken->Kind = kind;
    CurrentToken->Location = location;
    CurrentToken->Value = value;
    CurrentToken->Content = SourceCode.substr(startPos, NextCharIdx - startPos - 1);
}

void Lexer::GetNextChar() {
    if (NextCharIdx == SourceCode.size()) {
        CurrentChar = '\0';
        NextCharIdx++;
        return ;
    }
    CurrentChar = SourceCode[NextCharIdx++];
}

void Lexer::PrintAllToken() {
    GetNextToken();
    while (CurrentToken->Kind != TokenKind::Eof) {
        if (CurrentToken->Kind == TokenKind::Plus) printf("Add \n");
        else if (CurrentToken->Kind == TokenKind::Minus) printf("Sub \n");
        else if (CurrentToken->Kind == TokenKind::Star) printf("Mul \n");
        else if (CurrentToken->Kind == TokenKind::Slash) printf("Div \n");
        else if (CurrentToken->Kind == TokenKind::LParent) printf("(");
        else if (CurrentToken->Kind == TokenKind::RParent) printf(")");
        else if (CurrentToken->Kind == TokenKind::Num) printf("Num %d\n", CurrentToken->Value);
        
        GetNextToken();
    }
}

void Lexer::ExceptToken(TokenKind kind) {
    if (CurrentToken->Kind == kind) {
        GetNextToken();
    } else {
        DiagErr(SourceCode, CurrentToken->Location.Col, 
        "'%s' is execpted, but got '%s'.(line: %d)", 
        GetTokenKindSpellint(kind), GetTokenKindSpellint(CurrentToken->Kind), CurrentToken->Location.Line);
    }
}

void Lexer::SkipWhiteSpace() {
    while (isspace(CurrentChar) 
    || (CurrentChar == '/' && PeekChar(1) == '/')
    || (CurrentChar == '/' && PeekChar(1) == '*')) {
        if (CurrentChar == '/') {
            SkipComment();
            continue;
        } else if (CurrentChar == '\n') {
            Line++;
            LineHead = NextCharIdx;
        }
        GetNextChar();
    }
}

void Lexer::SkipComment() {
    if (CurrentChar == '/' && PeekChar(1) == '/') {
        while (CurrentChar != '\n') {
            GetNextChar();
        }
    } else {
        auto pos = SourceCode.find("*/", NextCharIdx + 1);
        if (pos == std::string_view::npos) {
            printf("unclosed comment! missing '*/' after '/*'\n");
            assert(0);
        } else {
            while (NextCharIdx != pos + 1) {
                
                if (CurrentChar == '\n') {
                    Line++;
                    LineHead = NextCharIdx;
                }
                GetNextChar();
            }
            
            CurrentChar = PeekChar((pos + 2) - (NextCharIdx - 1));
            NextCharIdx = pos + 3;
        }
    }
}

bool Lexer::IsLetter() {
    return (CurrentChar >= 'a' && CurrentChar <= 'z') 
           || (CurrentChar >= 'A' && CurrentChar <= 'Z')
           || (CurrentChar == '_'); 
}

bool Lexer::IsDigit() {
    return CurrentChar >= '0' && CurrentChar <= '9';
}

bool Lexer::IsLetterOrDigit() {
    return IsLetter() || IsDigit();
}

const char* Lexer::GetTokenKindSpellint(TokenKind kind) {
    switch (kind) {
    case TokenKind::Plus: return "+";
    case TokenKind::Minus: return "-";
    case TokenKind::Star: return "*";
    case TokenKind::Slash: return "/";
    case TokenKind::LParent: return "(";
    case TokenKind::RParent: return ")";
    case TokenKind::LBacket: return "[";
    case TokenKind::RBacket: return "]";
    case TokenKind::Semicolon: return ";";
    case TokenKind::Assign: return "=";
    case TokenKind::Equal: return "==";
    case TokenKind::Eof: return "eof";
    case TokenKind::PipeEqual: return "!=";
    case TokenKind::Greater: return ">";
    case TokenKind::GreaterEqual: return ">=";
    case TokenKind::Lesser: return "<";
    case TokenKind::LesserEqual: return "<=";
    case TokenKind::LBrace: return "{";
    case TokenKind::RBrace: return "}";
    case TokenKind::Comma: return ",";
    case TokenKind::If: return "if";
    case TokenKind::Else: return "else";
    case TokenKind::While: return "while";
    case TokenKind::Return: return "return";
    case TokenKind::Sizeof: return "sizeof";
    default:
        break;
    }
}

char Lexer::PeekChar(int distance) {
    assert(distance >= 0);
    if (NextCharIdx - 1 + distance < SourceCode.size()) {
        return SourceCode[NextCharIdx - 1 + distance];
    } else {
        return '\0';
    }
}

void Lexer::SaveStatus() {
    SavePointChar = CurrentChar;
    SavePointNextCharIdx = NextCharIdx;
    SavePointLine = Line;
    SavePointLineHead = LineHead;
    SavePointToken = CurrentToken;
}

void Lexer::LoadStatus() {
    CurrentChar = SavePointChar;
    NextCharIdx = SavePointNextCharIdx;
    Line = SavePointLine;
    LineHead = SavePointLineHead;
    CurrentToken = SavePointToken;
}